using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
using Parser;

namespace igp.net.DataModel
{
    /// <summary>
    /// Represents a set of points.
    /// </summary>
    public class igpPointSet : IgpItem
    {
        /// <summary>
        /// Default constructor.
        /// </summary>
        public igpPointSet()
        {
            TypeID = IgpItemType.PointSet;
            ItemName = "PointSet ";
            PointSize = 6;
            LineWidth = 1;
            LineColor = Color.Blue;
            FrameColor = Color.Black;
            FillColor = Color.White;
            Points = new ArrayList();
            IsVisible = true;
            VisibleOnLegend = true;

            DrawLines = true;
            DrawPoints = true;
            PointType = IgpPointType.Square;
            PointCoordinates = IgpCoordinates.none;
            EditMode = IgpPointEditMode.Add;
            CurrentMode = IgpPointEditMode.None;

            Driver = null;
            Filter = " [ x ; y ] ";
        }

        /// <summary>
        /// Gets/Sets the color of the points' borders.
        /// </summary>
        public Color FrameColor { get; set; }

        /// <summary>
        /// Gets/Sets the color the points are filled with.
        /// </summary>
        public Color FillColor { get; set; }

        /// <summary>
        /// Gets the list of the points contained in this set.
        /// </summary>
        public ArrayList Points { get; private set; }

        /// <summary>
        /// Enables/Disables drawing of the lines connecting points.
        /// </summary>
        public bool DrawLines { get; set; }

        /// <summary>
        /// Enables/Disables drawing of the points.
        /// </summary>
        public bool DrawPoints { get; set; }

        /// <summary>
        /// Size of the points in pixels.
        /// </summary>
        public int PointSize { get; set; }

        /// <summary>
        /// Determines style of the points.
        /// </summary>
        public IgpPointType PointType { get; set; }

        /// <summary>
        /// Sets/Gets position of the points' coordinates info.
        /// </summary>
        public IgpCoordinates PointCoordinates { get; set; }

        /// <summary>
        /// Determines user interaction mode.
        /// </summary>
        public IgpPointEditMode EditMode { get; set; }

        /// <summary>
        /// Gets the interaction mode that is being used at the moment.
        /// </summary>
        public IgpPointEditMode CurrentMode { get; internal set; }

        /// <summary>
        /// Gets/Sets the driver - a function, which values will be used as Y coordinates of the
        /// newly created and moved points.
        /// </summary>
        public IgpFunctionBase Driver { get; set; }

        /// <summary>
        /// Gets/Sets the format string of the coordinates info.
        /// </summary>
        public string Filter { get; set; }

        /// <summary>
        /// Adds a new point to the list.
        /// </summary>
        /// <param name="x">New point's X coordinate</param>
        /// <param name="y">New point's Y coordinate</param>
        public void AddPoint(double x, double y)
        {
            var item = new IgpPoint(x, y) {PointSet = this};
            Points.Add(item);
        }

        /// <summary>
        /// Draws the PointSet on the screen.
        /// </summary>
        /// <param name="graph">Graphics object used for painting</param>
        public override void Draw(Graphics graph)
        {
            var linepen = new Pen(LineColor, LineWidth) {DashStyle = LineType};

            if (DrawLines)
            {
                int x = 0;
                int y = 0;

                foreach (IgpPoint point in Points)
                {
                    if (Points.IndexOf(point) == 0)
                    {
                        x = IgpProject.Singleton.View.GetScreenCoordinateX(point.X);
                        y = IgpProject.Singleton.View.GetScreenCoordinateY(point.Y);
                        continue;
                    }

                    int oldx = x;
                    int oldy = y;

                    x = IgpProject.Singleton.View.GetScreenCoordinateX(point.X);
                    y = IgpProject.Singleton.View.GetScreenCoordinateY(point.Y);
                    graph.DrawLine(linepen, x, y, oldx, oldy);
                }
            }

            if (DrawPoints)
            {
                foreach (IgpPoint point in Points)
                {
                    point.Draw(graph);
                }
            }
        }

        /// <summary>
        /// This method is required for item's reaction to mouse buttons being pressed. 
        /// You must invoke this base method in your derived class in order 
        /// to update some useful properties.
        /// </summary>
        /// <param name="graph">Graphics object that can be used to paint something on the screen</param>
        /// <param name="e">Mouse Event Details</param>
        public override void InteractDown(Graphics graph, MouseEventArgs e)
        {
            base.InteractDown(graph, e);
            if (!IsVisible) return;
            IEnumerator item = Points.GetEnumerator();
            item.Reset();
            while (item.MoveNext())
            {
                ((IgpItem) item.Current).InteractDown(graph, e);
            }

            if (!InteractionInUse && EditMode == IgpPointEditMode.Add && CurrentMode == IgpPointEditMode.None &&
                e.Button == MouseButtons.Left)
            {
                double xp = IgpProject.Singleton.View.GetRealCoordinateX(e.X);
                double yp = Driver == null ? IgpProject.Singleton.View.GetRealCoordinateY(e.Y) : Driver.Value(xp);

                AddPoint(xp, yp);
                IgpProject.Singleton.UpdatePolynomials();
            }

            if (!InteractionInUse && EditMode == IgpPointEditMode.Select && CurrentMode == IgpPointEditMode.None && e.Button == MouseButtons.Left)
            {
                CurrentMode = IgpPointEditMode.SelectRect;
                InteractionInUse = true;
            }
        }

        /// <summary>
        /// This method is required for item's reaction to mouse movement. 
        /// You must invoke this base method in your derived class in order 
        /// to update some useful properties.
        /// </summary>
        /// <param name="graph">Graphics object that can be used to paint something on the screen</param>
        /// <param name="e">Mouse Event Details</param>
        public override void InteractMove(Graphics graph, MouseEventArgs e)
        {
            base.InteractMove(graph, e);

            if (!IsVisible) return;
            IEnumerator item = Points.GetEnumerator();
            
            if (EditMode != IgpPointEditMode.Add)
            {
                item.Reset();
                while (item.MoveNext())
                {
                    ((IgpItem) item.Current).InteractMove(graph, e);
                }
            }

            if (CurrentMode == IgpPointEditMode.SelectRect)
            {
                var rect = new Rectangle();
                IgpView view = IgpProject.Singleton.View;
                double ratiox = view.GetRatioX();
                double ratioy = view.GetRatioY();

                if (VectorX < 0 && VectorY < 0)
                    rect = new Rectangle((int) (MouseWhenPressed.X/ratiox),
                                         (int) (MouseWhenPressed.Y/ratioy),
                                         (int) (-VectorX/ratiox),
                                         (int) (-VectorY/ratioy));

                if (VectorX > 0 && VectorY < 0)
                    rect = new Rectangle(
                        (int) ((MouseWhenPressed.X - VectorX)/ratiox),
                        (int) (MouseWhenPressed.Y/ratioy),
                        (int) (VectorX/ratiox),
                        (int) (-VectorY/ratioy));

                if (VectorX < 0 && VectorY > 0)
                    rect = new Rectangle(
                        (int) (MouseWhenPressed.X/ratiox),
                        (int) ((MouseWhenPressed.Y - VectorY)/ratioy),
                        (int) (-VectorX/ratiox),
                        (int) (VectorY/ratioy));

                if (VectorX > 0 && VectorY > 0)
                    rect = new Rectangle(
                        (int) ((MouseWhenPressed.X - VectorX)/ratiox),
                        (int) ((MouseWhenPressed.Y - VectorY)/ratioy),
                        (int) (VectorX/ratiox),
                        (int) (VectorY/ratioy));

                graph.FillRectangle(new SolidBrush(Color.FromArgb(90, 255, 255, 255)), rect);
                graph.DrawRectangle(new Pen(view.Axes.LineColor), rect);

                item.Reset();
                
                while (item.MoveNext())
                {
                    ((IgpPoint) item.Current).IsSelected = rect.Contains(
                        view.GetScreenCoordinateX(((IgpPoint) item.Current).X),
                        view.GetScreenCoordinateY(((IgpPoint) item.Current).Y));
                }
            }
        }

        /// <summary>
        /// This method is required for item's reaction to mouse buttons being released. 
        /// </summary>
        /// <param name="graph">Graphics object that can be used to paint something on the screen</param>
        /// <param name="e">Mouse Event Details</param>
        public override void InteractUp(Graphics graph, MouseEventArgs e)
        {
            base.InteractUp(graph, e);
            
            if (!IsVisible) return;
            
            IEnumerator item = Points.GetEnumerator();
            item.Reset();
            
            while (item.MoveNext())
            {
                ((IgpItem) item.Current).InteractUp(graph, e);
            }

            if (CurrentMode == IgpPointEditMode.Select)
            {
                CurrentMode = IgpPointEditMode.None;
                InteractionInUse = false;
            }

            FormMain.Singleton.EditorWindow.ReadPointsList();
        }

        /// <summary>
        /// Gets the count of selected points.
        /// </summary>
        /// <returns>IsSelected points count</returns>
        public int SelectedCount()
        {
            int count = 0;

            foreach (IgpPoint point in Points) if (point.IsSelected) count++;
           
            return count;
        }

        /// <summary>
        /// Saves data to a TextWriter.
        /// </summary>
        /// <param name="sw">data output</param>
        public override void SaveData(TextWriter sw)
        {
            sw.WriteLine("<pointset>");
            sw.WriteLine("ItemName = {0}", ItemName);
            sw.WriteLine("LineColor = {0}", LineColor.ToArgb());
            sw.WriteLine("LineWidth = {0}", LineWidth);
            sw.WriteLine("DrawLines = {0}", DrawLines);
            sw.WriteLine("LineType = {0}", (int) LineType);

            sw.WriteLine("PointFrameColor = {0}", FrameColor.ToArgb());
            sw.WriteLine("PointFillColor = {0}", FillColor.ToArgb());
            sw.WriteLine("PointSize = {0}", PointSize);
            sw.WriteLine("DrawPoints = {0}", DrawPoints);
            sw.WriteLine("PointType = {0}", (int) PointType);
            sw.WriteLine("ShowCoordinates = {0}", (int) PointCoordinates);
            sw.WriteLine("Filter = {0}", Filter);

            sw.WriteLine("IsVisible = {0}", IsVisible);
            sw.WriteLine("OnLegend = {0}", VisibleOnLegend);

            IEnumerator item = Points.GetEnumerator();

            item.Reset();
            while (item.MoveNext())
            {
                var p = (IgpPoint) item.Current;
                sw.WriteLine("AddPoint = {0}|{1}", p.X, p.Y);
            }

            sw.WriteLine("</pointset>");
        }

        /// <summary>
        /// Loads data from a TextReader.
        /// </summary>
        /// <param name="sr">data input</param>
        public override void ReadData(TextReader sr)
        {
            String Line;
            while ((Line = sr.ReadLine()) != "</pointset>")
            {
                char[] sep = {'=', '|'};
                String[] cmd = Line.Split(sep, 3);

                if (cmd[0].StartsWith("ItemName"))
                {
                    ItemName = cmd[1].Substring(1, cmd[1].Length - 1);
                }

                if (cmd[0].StartsWith("Filter"))
                {
                    Filter = cmd[1].Substring(1, cmd[1].Length - 1);
                }

                if (cmd[0].StartsWith("LineColor"))
                {
                    LineColor = Color.FromArgb(int.Parse(cmd[1]));
                }

                if (cmd[0].StartsWith("LineWidth"))
                {
                    LineWidth = int.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("DrawLines"))
                {
                    DrawLines = bool.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("LineType"))
                {
                    LineType = (DashStyle) int.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("PointFrameColor"))
                {
                    FrameColor = Color.FromArgb(int.Parse(cmd[1]));
                }

                if (cmd[0].StartsWith("PointFillColor"))
                {
                    FillColor = Color.FromArgb(int.Parse(cmd[1]));
                }

                if (cmd[0].StartsWith("PointSize"))
                {
                    PointSize = int.Parse(cmd[1]);
                }
                if (cmd[0].StartsWith("DrawPoints"))
                {
                    DrawPoints = bool.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("PointType"))
                {
                    PointType = (IgpPointType) int.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("ShowCoordinates"))
                {
                    PointCoordinates = (IgpCoordinates) int.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("AddPoint"))
                {
                    AddPoint(double.Parse(cmd[1]), double.Parse(cmd[2]));
                }
                if (cmd[0].StartsWith("Visible"))
                {
                    IsVisible = bool.Parse(cmd[1]);
                }
                if (cmd[0].StartsWith("OnLegend"))
                {
                    VisibleOnLegend = bool.Parse(cmd[1]);
                }
            }
        }

        /// <summary>
        /// Pase points from clipboard.
        /// </summary>
        /// <returns>Inserted points count</returns>
        public int Paste()
        {
            if (Clipboard.GetDataObject() == null)
            {
                throw new Exception("No apropriate data in clipboard");
            }

            if (!Clipboard.GetDataObject().GetDataPresent(DataFormats.Text))
                throw new Exception("No apropriate data in clipboard");

            string cont = Clipboard.GetDataObject().GetData(DataFormats.Text).ToString();
            var sr = new StringReader(cont);
            char[] separator = {(char) 9};

            int a = 0;

            while (sr.Peek() > 0)
            {
                string line = sr.ReadLine();
                string[] datas = line.Split(separator, 2);
                AddPoint(double.Parse(datas[0]), double.Parse(datas[1]));
                a++;
            }

            return a;
        }

        /// <summary>
        /// Copies points to clipboard.
        /// </summary>
        public void Copy()
        {
            string clip = "";

            foreach (IgpPoint pt in Points)
            {
                clip += pt.X.ToString() + '\t' + pt.Y + "\r\n";
            }

            Clipboard.SetDataObject(clip);
        }

        ///<summary>
        /// Sort points based on their X coordinate.
        ///</summary>
        public void SortPoints()
        {
            Points.Sort(new igpPointComparer());
        }

        ///<summary>
        /// Transforms points coordinates according to an expression specified.
        ///</summary>
        ///<param name="expression"></param>
        ///<param name="affectX"></param>
        ///<param name="affectY"></param>
        public void TransformPoints(string expression, bool affectX, bool affectY)
        {
            var expr = new Expression(expression, new[] { "x" });
            expr.SetConstant("x", 0);
            expr.Validate();

            foreach (IgpPoint pt in Points)
            {
                if (affectX)
                {
                    expr.SetConstant("x", pt.X);
                    pt.X = expr.Parse();
                }

                if (affectY)
                {
                    expr.SetConstant("x", pt.Y);
                    pt.Y = expr.Parse();
                }
            }

        }
    }
}